//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <core.h>
#include <_hal.h>
#include "common.h"
#include "exception.h"
#include "scgate.h"

const uint_t c_uNumberOfInterrupts  = 16;
const uint_t c_uNumberOfIpls        = IPL31 + 1;

typedef struct IsrDescriptor {
    ipl_t   ipl;
    isr_t   isr;
    void    *pvDevice;
} IsrDescriptor;

EXTERN ipl_t            g_currentIpl;
EXTERN irqs_t           g_irqsTable[c_uNumberOfIpls + 1];
EXTERN irqs_t           g_activeIrqs;
EXTERN IsrDescriptor    g_isrDescriptors[c_uNumberOfInterrupts];

ipl_t           g_currentIpl = IPL_Base;
irqs_t          g_irqsTable[c_uNumberOfIpls + 1];
irqs_t          g_activeIrqs = 0;
IsrDescriptor   g_isrDescriptors[c_uNumberOfInterrupts];

DECL_ASMLINKAGE void StrayInterruptHandler(void);

STATIC DECL_CODEINIT void CDECL InitIDT()
{
    for (vector_t v = 0; v < c_uNumberOfIdtEntries; v++) {
        SetInterruptGate(v, (virtaddr_t)&StrayInterruptHandler);
    }
}

STATIC void CDECL NoiseDetectIsr(
    irq_t irq, void *pvDevice, InterruptContext *pContext)
{
    kprintf("*WARNING* spurious noise glitches on the IR inputs\n");
}

DECL_ASMLINKAGE void InterruptHandler0();
DECL_ASMLINKAGE void InterruptHandler1();
DECL_ASMLINKAGE void InterruptHandler2();
DECL_ASMLINKAGE void InterruptHandler3();
DECL_ASMLINKAGE void InterruptHandler4();
DECL_ASMLINKAGE void InterruptHandler5();
DECL_ASMLINKAGE void InterruptHandler6();
DECL_ASMLINKAGE void InterruptHandler7();
DECL_ASMLINKAGE void InterruptHandler8();
DECL_ASMLINKAGE void InterruptHandler9();
DECL_ASMLINKAGE void InterruptHandler10();
DECL_ASMLINKAGE void InterruptHandler11();
DECL_ASMLINKAGE void InterruptHandler12();
DECL_ASMLINKAGE void InterruptHandler13();
DECL_ASMLINKAGE void InterruptHandler14();
DECL_ASMLINKAGE void InterruptHandler15();

STATIC DECL_CODEINIT void CDECL InitExternalInterrupts()
{
#define SET_INTERRUPT_HANDLER(n) \
    SetInterruptGate(bsp.uIrqBase + n, (virtaddr_t)&InterruptHandler##n)

    SET_INTERRUPT_HANDLER(0);
    SET_INTERRUPT_HANDLER(1);
    SET_INTERRUPT_HANDLER(2);
    SET_INTERRUPT_HANDLER(3);
    SET_INTERRUPT_HANDLER(4);
    SET_INTERRUPT_HANDLER(5);
    SET_INTERRUPT_HANDLER(6);
    SET_INTERRUPT_HANDLER(7);
    SET_INTERRUPT_HANDLER(8);
    SET_INTERRUPT_HANDLER(9);
    SET_INTERRUPT_HANDLER(10);
    SET_INTERRUPT_HANDLER(11);
    SET_INTERRUPT_HANDLER(12);
    SET_INTERRUPT_HANDLER(13);
    SET_INTERRUPT_HANDLER(14);
    SET_INTERRUPT_HANDLER(15);

    for (uint_t i = 0; i < c_uNumberOfIpls + 1; i++) {
        g_irqsTable[i] = 0;
    }

    BspMaskAllIrqs(FALSE);

    DzRegisterIsr(7, MIN_IPL, &NoiseDetectIsr, NULL);

    Sti();
}

EXTERN DECL_CODEINIT bool_t CDECL InitInterrupts()
{
    InitIDT();

    InitExternalInterrupts();

    InitExceptions();

    InitSystemCall();

    return TRUE;
}

EXTERN void CDECL DzRegisterIsr(irq_t irq, ipl_t ipl, isr_t isr,
    void *pvDevice)
{
    assert(c_uNumberOfInterrupts > irq);
    assert(IPL31 >= ipl);
    assert(isr);

    uint32_t uFlags = SaveFlagsAndCli();

    if (NULL != g_isrDescriptors[irq].isr) DzUnregisterIsr(irq);

    g_isrDescriptors[irq].ipl       = ipl;
    g_isrDescriptors[irq].isr       = isr;
    g_isrDescriptors[irq].pvDevice  = pvDevice;

    for (uint_t i = 0; i <= g_isrDescriptors[irq].ipl; i++) {
        g_irqsTable[i] |= (irqs_t)1 << irq;
    }

    DzEnableIrq(irq);

    RestoreIF(uFlags);
}

EXTERN void CDECL DzUnregisterIsr(irq_t irq)
{
    assert(c_uNumberOfInterrupts > irq);
    assert(g_isrDescriptors[irq].isr);

    uint32_t uFlags = SaveFlagsAndCli();

    DzDisableIrq(irq);

    for (uint_t i = 0; i <= g_isrDescriptors[irq].ipl; i++) {
        g_irqsTable[i] &= ~((irqs_t)1 << irq);
    }

    g_isrDescriptors[irq].isr = NULL;

    RestoreIF(uFlags);
}

EXTERN void CDECL DzEnableIrq(irq_t irq)
{
    assert(c_uNumberOfInterrupts > irq);

    uint32_t uFlags = SaveFlagsAndCli();
    if (NULL != g_isrDescriptors[irq].isr) {
        g_activeIrqs |= (irqs_t)1 << irq;

        if (g_isrDescriptors[irq].ipl < g_currentIpl) {
            BspMaskIrqs((irqs_t)1 << irq, TRUE);
        }
    }
    RestoreIF(uFlags);
}

EXTERN void CDECL DzDisableIrq(irq_t irq)
{
    assert(c_uNumberOfInterrupts > irq);

    uint32_t uFlags = SaveFlagsAndCli();
    if (NULL != g_isrDescriptors[irq].isr) {
        if (g_isrDescriptors[irq].ipl < g_currentIpl) {
            BspMaskIrqs((irqs_t)1 << irq, FALSE);
        }

        g_activeIrqs &= ~((irqs_t)1 << irq);
    }
    RestoreIF(uFlags);
}

EXTERN ipl_t CDECL DzRaiseCurrentIpl(ipl_t ipl)
{
    assert(IPL31 >= ipl);

    uint32_t uFlags = SaveFlagsAndCli();

    register ipl_t prevIpl = g_currentIpl;

    if (ipl < g_currentIpl) {
        BspMaskIrqs(
            (g_irqsTable[ipl] - g_irqsTable[g_currentIpl]) & g_activeIrqs,
            FALSE);
        g_currentIpl = ipl;
    }

    RestoreIF(uFlags);

    return prevIpl;
}

EXTERN ipl_t CDECL DzLowerCurrentIpl(ipl_t ipl)
{
    assert(IPL1 <= ipl && ipl <= IPL_Base);

    uint32_t uFlags = SaveFlagsAndCli();

    register ipl_t prevIpl = g_currentIpl;

    if (ipl > g_currentIpl) {
        BspMaskIrqs(
            (g_irqsTable[g_currentIpl] - g_irqsTable[ipl]) & g_activeIrqs,
            TRUE);
        g_currentIpl = ipl;
    }

    RestoreIF(uFlags);

    return prevIpl;
}

EXTERN void __InterruptHandlers()
{
#define DEFINE_INTERRUPT_HANDLER(n)         \
    ASM(".global _InterruptHandler"#n";"    \
        ".align;"                           \
        "_InterruptHandler"#n":"            \
        "push   %0;"                        \
        "jmp    _CommonInterruptHandler;"   \
        : :"i"(n))

    DEFINE_INTERRUPT_HANDLER(0);
    DEFINE_INTERRUPT_HANDLER(1);
    DEFINE_INTERRUPT_HANDLER(2);
    DEFINE_INTERRUPT_HANDLER(3);
    DEFINE_INTERRUPT_HANDLER(4);
    DEFINE_INTERRUPT_HANDLER(5);
    DEFINE_INTERRUPT_HANDLER(6);
    DEFINE_INTERRUPT_HANDLER(7);
    DEFINE_INTERRUPT_HANDLER(8);
    DEFINE_INTERRUPT_HANDLER(9);
    DEFINE_INTERRUPT_HANDLER(10);
    DEFINE_INTERRUPT_HANDLER(11);
    DEFINE_INTERRUPT_HANDLER(12);
    DEFINE_INTERRUPT_HANDLER(13);
    DEFINE_INTERRUPT_HANDLER(14);
    DEFINE_INTERRUPT_HANDLER(15);

DECL_ASMENTRY(CommonInterruptHandler)

    __SAVE_SOFTWARE_REGS();

    __SETUP_KERNEL_REGS();

    TIMEPEG_INTERRUPT_START

    ASM("call   _HandleInterrupt;");

DECL_ASMENTRY(ReturnFromInterrupt)

    if (PreemptionEnabled() &&
        InterlockedExchange(&GetCurrentThread()->m_bNeedReschedule, FALSE)) {
        Schedule();
    }

DECL_ASMENTRY(RestoreContext)

    __RESTORE_SOFTWARE_REGS();

    ASM("addl   $4, %esp;"      // pop InterruptContext::u
        "iret;");
}

DECL_ASMLINKAGE void CDECL UpdateTime(InterruptContext *pContext);

STATIC uint_t s_uInterruptNested = 0;

EXTERN bool_t CDECL DzIsInIsr()
{
    return s_uInterruptNested > 0;
}

DECL_ASMLINKAGE void CDECL HandleInterrupt(InterruptContext context)
{
    assert(THREAD_END_MARKER == GetCurrentThread()->m_uEndMarker);

    register irq_t irq = context.u.irq;
    assert(g_isrDescriptors[irq].isr);

    register ipl_t prevIpl = g_currentIpl;

    assert(g_isrDescriptors[irq].ipl < g_currentIpl);

    g_currentIpl = g_isrDescriptors[irq].ipl;
    BspMaskIrqs(
        (g_irqsTable[g_currentIpl] - g_irqsTable[prevIpl]) & g_activeIrqs,
        FALSE);

    BspAcknowledgeIrq(irq);

    bool_t bOriginalPreemption = DzDisablePreemption();
    s_uInterruptNested++;
    Sti();

    (*g_isrDescriptors[irq].isr)(
           irq, g_isrDescriptors[irq].pvDevice, &context);

    Sti();

    // TODO: need optimize?
    Cli();
    s_uInterruptNested--;
    BspMaskIrqs(
        (g_irqsTable[g_currentIpl] - g_irqsTable[prevIpl]) & g_activeIrqs,
        TRUE);
    g_currentIpl = prevIpl;
    Sti();

    RestorePreemptionByIsr(bOriginalPreemption);

    if (0 == s_uInterruptNested && 0 < g_uLostTicks) {
        UpdateTime(&context);
    }
}

EXTERN void __StrayInterruptHandler()
{
DECL_ASMENTRY(StrayInterruptHandler)

    __SAVE_SOFTWARE_REGS();

    __SETUP_KERNEL_REGS();

    STATIC uint_t s_uNumberOfStrayInterrupts = 0;
    kprintf("*INTERRUPT* Stray Interrupt (count: %d)\n",
            s_uNumberOfStrayInterrupts++);

    __RESTORE_SOFTWARE_REGS();

    ASM("iret;");
}
